﻿using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net;
using Extention.ENode.Rpc.Runtime.Communally.Entitys.Messages;
using Extention.ENode.Rpc.Runtime.Server;
using Extention.ENode.Rpc.Transport.Codec;
using Extention.ENode.Rpc.Transport.InternalAdaper;
using DotNetty.Codecs;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using Microsoft.Extensions.Logging;
using DotNetty.Buffers;
using System.Threading.Tasks;


namespace Extention.ENode.Rpc.Transport.Impl
{
    /// <summary>
    /// 基于DotNetty的传输客户端工厂
    /// </summary>
    public class DefaultDotNettyTransportClientFactory : ITransportClientFactory, IDisposable
    {
        private readonly ITransportMessageEncoder _transportMessageEncoder;
        private readonly ITransportMessageDecoder _transportMessageDecoder;
        private readonly IServiceExecutor _serviceExecutor;
        private readonly ILogger<DefaultDotNettyTransportClientFactory> _logger;

        private readonly ConcurrentDictionary<EndPoint, Lazy<Task<ITransportClient>>> _clients =
            new ConcurrentDictionary<EndPoint, Lazy<Task<ITransportClient>>>();

        private readonly Bootstrap _bootstrap;

        private static readonly AttributeKey<IMessageSender> MessageSenderKey =
            AttributeKey<IMessageSender>.ValueOf(typeof(DefaultDotNettyTransportClientFactory), nameof(IMessageSender));

        private static readonly AttributeKey<IMessageListener> MessageListenerKey =
            AttributeKey<IMessageListener>.ValueOf(typeof(DefaultDotNettyTransportClientFactory),
                nameof(IMessageListener));

        private static readonly AttributeKey<EndPoint> OrigEndPointKey =
            AttributeKey<EndPoint>.ValueOf(typeof(DefaultDotNettyTransportClientFactory), nameof(EndPoint));

        public DefaultDotNettyTransportClientFactory(ITransportMessageCodecFactory codecFactory,
            ILogger<DefaultDotNettyTransportClientFactory> logger,
            IServiceExecutor serviceExecutor = null)
        {
            _logger = logger;
            _transportMessageEncoder = codecFactory.GetEncoder();
            _transportMessageDecoder = codecFactory.GetDecoder();
            _serviceExecutor = serviceExecutor;
            _bootstrap = GetBootstrap();
            _bootstrap.Handler(new ActionChannelInitializer<ISocketChannel>(c =>
            {
                var pipeline = c.Pipeline;
                pipeline.AddLast(new LengthFieldPrepender(4));
                pipeline.AddLast(new LengthFieldBasedFrameDecoder(int.MaxValue, 0, 4, 0, 4));
                pipeline.AddLast(new TransportMessageChannelHandlerDecodeAdapter(_transportMessageDecoder));
                pipeline.AddLast(new DefaultChannelHandler(this));
            }));

        }



        /// <summary>
        /// 创建客户端
        /// </summary>
        /// <param name="endPoint">终结点</param>
        /// <returns>传输客户端实例</returns>
        public async Task<ITransportClient> CreateClient(EndPoint endPoint)
        {


            var key = endPoint;
            if (_logger.IsEnabled(LogLevel.Debug))
                _logger.LogDebug($"准备为服务端地址：{key}创建客户端。");
            try
            {
                return await _clients.GetOrAdd(key
                    , k => new Lazy<Task<ITransportClient>>(async () =>
                    {
                        //客户端对象
                        var bootstrap = _bootstrap;
                        //异步连接返回channel
                        var channel = await bootstrap.ConnectAsync(k);
                        var messageListener = new MessageListener();
                        //设置监听
                        channel.GetAttribute(MessageListenerKey).Set(messageListener);
                        //实例化发送者
                        var messageSender = new DefaultDotNettyMessageClientSender(_transportMessageEncoder, channel);
                        //设置channel属性
                        channel.GetAttribute(MessageSenderKey).Set(messageSender);
                        channel.GetAttribute(OrigEndPointKey).Set(k);
                        //创建客户端
                        var client = new TransportClient(messageSender, messageListener, _serviceExecutor, _logger);
                        return client;
                    }
                    )).Value;//返回实例
            }
            catch
            {
                //移除
                _clients.TryRemove(key, out var value);
                //var ipEndPoint = endPoint as IPEndPoint;
                ////标记这个地址是失败的请求
                //if (ipEndPoint != null)
                //    await _healthCheckService.MarkFailure(new IpAddressModel(ipEndPoint.Address.ToString(), ipEndPoint.Port));
                throw;
            }

           
        }


        /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
        public void Dispose()
        {
            foreach (var client in _clients.Values)
            {
                (client as IDisposable)?.Dispose();
            }
        }

        private static Bootstrap GetBootstrap()
        {
            IEventLoopGroup group;

            var bootstrap = new Bootstrap();

            group = new MultithreadEventLoopGroup();
            bootstrap.Channel<TcpServerSocketChannel>();

            bootstrap
                .Channel<TcpSocketChannel>()
                .Option(ChannelOption.TcpNodelay, true)
                .Option(ChannelOption.Allocator, PooledByteBufferAllocator.Default)
                .Group(group);
            return bootstrap;
        }

        public class DefaultChannelHandler : ChannelHandlerAdapter
        {
            private readonly DefaultDotNettyTransportClientFactory _factory;

            public DefaultChannelHandler(DefaultDotNettyTransportClientFactory factory)
            {
                this._factory = factory;
            }


            public override void ChannelInactive(IChannelHandlerContext context)
            {
                _factory._clients.TryRemove(context.Channel.GetAttribute(OrigEndPointKey).Get(), out var value);
            }

            public override void ChannelRead(IChannelHandlerContext context, object message)
            {
                var transportMessage = message as TransportMessage;

                var messageListener = context.Channel.GetAttribute(MessageListenerKey).Get();
                var messageSender = context.Channel.GetAttribute(MessageSenderKey).Get();
                messageListener.OnReceived(messageSender, transportMessage);
            }

        }
    }
}